# -*- coding: utf-8 -*-
# owner: shenyi
# file: src/providers/aws/ec2.py

import asyncio
from typing import Optional, List
from cachetools import TTLCache
from botocore.exceptions import ClientError

from src.providers.base import BaseProvider
from src.models.instance import (
    CreateInstanceRequest,
    DeleteInstanceRequest,
    InstanceTypeRequest,
    AvailableInstanceTypeRequest,
)
from src.models.responses import (
    CreateResponse,
    DeleteResponse,
    InstanceTypeResponse,
    AvailableZonesInstanceTypeResponse,
    InstanceType as ResponseInstanceType,
    AvailableZoneInstanceType,
)
from src.strategies import ExcludeInstanceFamilyStrategy, CpuStrategy, MemoryStrategy
from src.core import logger
from .client import Client


# 全局缓存（注意：在多区域/多用户场景下建议使用更细粒度缓存）
_instance_type_cache = TTLCache(maxsize=100, ttl=60)


class Ec2(BaseProvider):
    """
    AWS EC2 实现类，继承自 BaseProvider
    """

    def __init__(self, **config):
        super().__init__(**config)
        # 可选：注入 client 用于测试
        self._client = config.get("client") or Client.ec2_client()

    async def create_instance(self, request: CreateInstanceRequest) -> CreateResponse:
        """
        创建 EC2 实例
        """
        block_devices = []
        for disk in request.data_disk or []:
            block_devices.append({
                'DeviceName': disk.device_name,
                'Ebs': {
                    'DeleteOnTermination': True,
                    'VolumeSize': disk.size,
                    'VolumeType': disk.type or 'gp3',
                }
            })

        launch_template = {}
        if request.launch_template_id:
            launch_template = {
                'LaunchTemplateId': request.launch_template_id,
                'Version': request.launch_template_version or '$Default'
            }

        try:
            # 使用 asyncio.to_thread 避免阻塞事件循环（boto3 是同步库）
            response = await asyncio.to_thread(
                self._client.create_instances,
                BlockDeviceMappings=block_devices,
                ImageId=request.image_id,
                InstanceType=request.instance_type,
                LaunchTemplate=launch_template,
                TagSpecifications=[
                    {
                        'ResourceType': 'instance',
                        'Tags': [{'Key': 'Name', 'Value': request.host_name or 'unnamed'}]
                    }
                ],
                MinCount=1,
                MaxCount=1
            )
            instance_id = response['Instances'][0]['InstanceId']
            return CreateResponse(instance_ids=[instance_id])
        except Exception as e:
            error_msg = str(e)
            logger.error(f"Failed to create EC2 instance: {error_msg}")
            return CreateResponse(status_code=400, message=error_msg)

    async def delete_instance(self, request: DeleteInstanceRequest) -> DeleteResponse:
        """
        终止 EC2 实例
        """
        try:
            await asyncio.to_thread(
                self._client.terminate_instances,
                InstanceIds=[request.instance_id]
            )
            return DeleteResponse(status_code=200, message="success")
        except ClientError as e:
            error = e.response.get('Error', {})
            code = error.get('Code')
            if code == 'InvalidInstanceID.NotFound':
                status_code = 404
            else:
                status_code = 500
            msg = error.get('Message', 'Unknown error')
            logger.error(f"Failed to delete EC2 instance {request.instance_id}: {msg}")
            return DeleteResponse(status_code=status_code, message=msg)
        except Exception as e:
            logger.error(f"Unexpected error deleting EC2 instance: {e}")
            return DeleteResponse(status_code=500, message=str(e))

    async def list_instance_types(self, request: InstanceTypeRequest) -> InstanceTypeResponse:
        """
        查询 EC2 实例类型（支持分页、过滤、缓存）
        """
        region = self._client.region_name
        if not region:
            return InstanceTypeResponse(status_code=400, message="Region is required", instance_spec=[])

        arch = "arm64" if (request.cpu_architecture or "").lower() == "arm" else "x86_64"
        cache_key = f"ec2_instance_types_{region}_{arch}_{request.zone_id or 'any'}"

        if cache_key in _instance_type_cache:
            instance_types = _instance_type_cache[cache_key]
        else:
            filters = [{'Name': 'processor-info.supported-architecture', 'Values': [arch]}]
            if request.instance_type_families:
                families = request.instance_type_families.split(",")
                # 转换为通配符格式（如 t3 -> t3.*）
                patterns = [f"{f}.*" for f in families]
                filters.append({'Name': 'instance-type', 'Values': patterns})

            paginator = self._client.get_paginator('describe_instance_types')
            instance_types = []
            try:
                for page in paginator.paginate(Filters=filters):
                    instance_types.extend(page['InstanceTypes'])
                    if len(instance_types) > 200:  # 防止过长
                        break
                _instance_type_cache[cache_key] = instance_types
            except Exception as e:
                logger.error(f"Failed to fetch instance types from AWS: {e}")
                return InstanceTypeResponse(status_code=500, message=str(e), instance_spec=[])

        filtered = []
        for it in instance_types:
            vcpu = it['VCpuInfo']['DefaultVCpus']
            mem_gib = it['MemoryInfo']['SizeInMiB'] / 1024.0

            # 策略过滤
            if CpuStrategy.filter(vcpu, request.mini_cpu_core_count):
                continue
            if MemoryStrategy.filter(mem_gib, request.mini_memory_size):
                continue
            if vcpu > 256:
                continue
            if request.exclude_families and ExcludeInstanceFamilyStrategy.exclude(
                it['InstanceType'], request.exclude_families
            ):
                continue

            filtered.append(ResponseInstanceType(
                instance_type_id=it['InstanceType'],
                cpu_architecture=it['ProcessorInfo']['SupportedArchitectures'][0],
                memory_size=mem_gib,
                cpu_core_count=vcpu,
            ))

        logger.info(f"Fetched {len(filtered)} EC2 instance types for region {region}")
        return InstanceTypeResponse(status_code=200, message="success", instance_spec=filtered)

    async def list_available_instance_types(
        self, request: AvailableInstanceTypeRequest
    ) -> AvailableZonesInstanceTypeResponse:
        """
        查询指定可用区中可用的实例类型
        """
        if  not request.zone_id:
            return AvailableZonesInstanceTypeResponse(
                status_code=400,
                message="zone_id are required",
                available_zone=[]
            )

        try:
            response = await asyncio.to_thread(
                self._client.describe_instance_type_offerings,
                LocationType='availability-zone',
                Filters=[
                    {'Name': 'location', 'Values': [request.zone_id]},
                    {'Name': 'instance-type', 'Values': ['*']}  # 可选，显式列出
                ]
            )
            results = []
            for offering in response.get('InstanceTypeOfferings', []):
                results.append(AvailableZoneInstanceType(
                    available=True,
                    instance_type_id=offering['InstanceType'],
                    zone_id=offering['Location']
                ))
            return AvailableZonesInstanceTypeResponse(
                status_code=200,
                message="success",
                available_zone=results
            )
        except Exception as e:
            error_msg = str(e)
            logger.error(f"Failed to list available EC2 instance types in {request.zone_id}: {error_msg}")
            return AvailableZonesInstanceTypeResponse(
                status_code=500,
                message=error_msg,
                available_zone=[]
            )

    async def health_check(self) -> bool:
        """健康检查：调用 describe_regions"""
        try:
            await asyncio.to_thread(self._client.describe_regions, MaxResults=5)
            return True
        except Exception:
            return False